module Ransack
  module Nodes
    class Value < Node
      attr_accessor :value
      delegate :present?, :blank?, :to => :value

      def initialize(context, value = nil)
        super(context)
        @value = value
      end

      def persisted?
        false
      end

      def eql?(other)
        self.class == other.class && self.value == other.value
      end
      alias :== :eql?

      def hash
        value.hash
      end

      def cast(type)
         case type
         when :date
           cast_to_date(value)
         when :datetime, :timestamp, :time
           cast_to_time(value)
         when :boolean
           cast_to_boolean(value)
         when :integer
           cast_to_integer(value)
         when :float
           cast_to_float(value)
         when :decimal
           cast_to_decimal(value)
         else
           cast_to_string(value)
         end
       end

       def cast_to_date(val)
         if val.respond_to?(:to_date)
           val.to_date rescue nil
         else
           y, m, d = *[val].flatten
           m ||= 1
           d ||= 1
           Date.new(y,m,d) rescue nil
         end
       end

       def cast_to_time(val)
         if val.is_a?(Array)
           Time.zone.local(*val) rescue nil
         else
           unless val.acts_like?(:time)
             val = val.is_a?(String) ? Time.zone.parse(val) : val.to_time rescue val
           end
           val.in_time_zone rescue nil
         end
       end

       def cast_to_boolean(val)
         if Constants::TRUE_VALUES.include?(val)
           true
         elsif Constants::FALSE_VALUES.include?(val)
           false
         else
           nil
         end
       end

       def cast_to_string(val)
         val.respond_to?(:to_s) ? val.to_s : String.new(val)
       end

       def cast_to_integer(val)
         val.blank? ? nil : val.to_i
       end

       def cast_to_float(val)
         val.blank? ? nil : val.to_f
       end

       def cast_to_decimal(val)
         if val.blank?
           nil
         elsif val.class == BigDecimal
           val
         elsif val.respond_to?(:to_d)
           val.to_d
         else
           val.to_s.to_d
         end
       end

      def inspect
        "Value <#{value}>"
      end

       def array_of_arrays?(val)
         Array === val && Array === val.first
       end
    end
  end
end
